/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.misc.dts.util;


import org.vacoor.nothing.common.util.IOUtils;
import org.vacoor.nothing.common.util.Throwables;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 一个简单的 PDF 工具类, 主要用于获取PDF文件的总页数
 * 参考自FlexPaper war文件 进行修改
 * 速度要比 iText中PdfReader.getNumberOfPages 快的多
 *
 * @author vacoor
 */
@SuppressWarnings({"unused"})
public abstract class PDFUtils {
    /*-
     * PDF文件结构:
     * 1.文件头
     * 	 第一行格式如下,标识遵循的PDF规范版本
     *   %PDF-1.4
     * 2.对象集合
     * 3.交叉引用表
     * 4.trailer(包含字典,作者,标题,等)
     *   最后一行以%%EOF结尾
     */

    // -----------------
    // 1.5 也有.
    /*- PDF 1.3/1.6版 前几行会有如下标识,其中/N {numcout}标识总页数
     *  <</Linearized 1/L 505476/O 140/E 177700/N 28/S 504869/H [ 530 726]>>
     *  <</Linearized 1/O 824/H [ 596 551]/L 35602165/E 143759/N 156/T 35585610>>
     */

    // 文件头 %PDF-{version}
    public static final byte[] PDF_HEADER = {0x25, 0x50, 0x44, 0x46, 0x2D}; // %, P, D, F, -

    // 有一部分不行
    private static final Pattern PAGE_PATTERN_1 = Pattern.compile("/Linearized\\s+[^T]*\\s+([0-9]+)/T", Pattern.MULTILINE);
    private static final Pattern PAGE_PATTERN_2 = Pattern.compile("Count ([0-9]+)/Type/Pages/Kids");
    // 有可能分多行
    private static final Pattern PAGE_PATTERN_3 = Pattern.compile("/Type\\s*/Pages\\s*/Count\\s+([0-9]+)\\s*/Kids", Pattern.MULTILINE); //
    private static final Pattern PAGE_PATTERN_6 = Pattern.compile("/Type/Pages/Kids[^/]*/Count ([0-9]+)>>", Pattern.MULTILINE); //
    private static final Pattern PAGE_PATTERN_7 = Pattern.compile("^/Count ([0-9]+)/Type/Pages>>");
    private static final Pattern PAGE_PATTERN_8 = Pattern.compile("^/Count ([0-9]+)>>$");
    private static final Pattern PAGE_PATTERN_9 = Pattern.compile("^/Length ([0-9]+)\\s*$");
    private static final Pattern PAGE_PATTERN_10 = Pattern.compile("/Count ([0-9]+)/Kids[^/]*/Type/Pages");

    // Count 55/Type/Pages/Kids

    /*
        1.4/1.5 会有如下标识, 但可能是多行
        <</Type/Pages/Count 476/Kids [....]>>
        <</Type/Pages
        /Resource2s 116 0 R
        /MediaBox[ 0 0 842 595 ]
        /Kids[ 1 0 R 16 0 R 23 0 R 26 0 R 29 0 R 32 0 R 35 0 R 38 0 R 41 0 R 44 0 R 47 0 R 50 0 R 53 0 R 56 0 R 59 0 R 62 0 R
        65 0 R ]
        /Count 17>>
    */
    //private static final Pattern PAGE_PATTERN_4 = Pattern.compile("(?:/Type\\s*/Pages[^>]*/Count\\s+)([0-9]+)", Pattern.MULTILINE); //
//    private static final Pattern PAGE_PATTERN_4 = Pattern.compile("^Count ([0-9]+)$", Pattern.MULTILINE); //
    // /Type/Pages/Count 293/Kids
    private static final Pattern PAGE_PATTERN_4 = Pattern.compile("^<</Count ([0-9]+)/First"); //
    private static final Pattern PAGE_PATTERN_5 = Pattern.compile("^\\s*/Count ([0-9]+)\\s*$"); //
    // 1.4 ^/Count page$ or ^<</Count page/First... (唯一) or ^Count page>>endobj$
    // 1.5 ^/Count page$ or

    /**
     * 根据文件头(前五个字节) 判断是否是 pdf
     *
     * @param header
     * @return
     */
    public static boolean isPdfHeader(byte[]/*len=5*/ header) {
        return header != null &&
                header.length >= PDF_HEADER.length &&
                PDF_HEADER[0] == header[0] &&
                PDF_HEADER[1] == header[1] &&
                PDF_HEADER[2] == header[2] &&
                PDF_HEADER[3] == header[3] &&
                PDF_HEADER[4] == header[4];
    }

    /**
     * 获取PDF 文件的总页数
     * <p/>
     */
    public static int getTotalPages(InputStream is) {
        String l1, l2, l3;
        l1 = l2 = l3 = "";

        int page = -1;
        int secondPage = -1;

        try {
            byte[] header = new byte[PDF_HEADER.length];
            is.read(header);
            if (!isPdfHeader(header)) {
                return -1;
            }

            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "US-ASCII"));
            String line;
            while (null != (line = reader.readLine())) {
                l1 = l2;
                l2 = l3;
                l3 = line;

                if (-1 != (page = extractTotalPages(line))) {
                    break;
                }
                if (-1 != (page = extractTotalPages(l1 + l2 + l3))) {
                    break;
                }

                int p = extraSecondPage(line);
                if (-1 != p) {
                    /*
                    secondPage = p;
                    secondPageCount++;
                    */
                    secondPage = secondPage > p ? secondPage : p;
                }
            }
        } catch (IOException e) {
            throw Throwables.unchecked(e);
        } finally {
            IOUtils.close(is);
        }

        page = page > -1 ? page : secondPage;
        return page;
    }

    private static int extractTotalPages(CharSequence charseq) {
        int page = -1;

        Matcher matcher = PAGE_PATTERN_1.matcher(charseq);
        if (matcher.find()) {
            page = Integer.parseInt(matcher.group(1).trim());
        }

        if (-1 == page) {
            matcher = PAGE_PATTERN_2.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }

        if (-1 == page) {
            matcher = PAGE_PATTERN_3.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }

        if (-1 == page) {
            matcher = PAGE_PATTERN_4.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }

        if (-1 == page) {
            matcher = PAGE_PATTERN_6.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }
        if (-1 == page) {
            matcher = PAGE_PATTERN_7.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }
        if (-1 == page) {
            matcher = PAGE_PATTERN_8.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }
        if (-1 == page) {
            matcher = PAGE_PATTERN_9.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }
        if (-1 == page) {
            matcher = PAGE_PATTERN_10.matcher(charseq);
            if (matcher.find()) {
                page = Integer.parseInt(matcher.group(1).trim());
            }
        }

        return page;
    }

    private static int extraSecondPage(String charseq) {
        int page = -1;
        Matcher matcher = PAGE_PATTERN_5.matcher(charseq);
        if (matcher.find()) {
            page = Integer.parseInt(matcher.group(1).trim());
        }
        return page;
    }

    private PDFUtils() {
    }

    public static void main(String[] args) throws FileNotFoundException {
        int totalPages = getTotalPages(new FileInputStream(System.getProperty("user.home") + "/Desktop/decode.pdf"));
        System.out.println(totalPages);
        System.exit(0);
        String file = System.getProperty("user.home") + "/Desktop/store/" + "decode.pdf";
        File docs = new File(System.getProperty("user.home"), "Documents");
        File[] files = docs.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".pdf");
            }
        });
        System.out.println(files.length);

        for (File f : files) {
            FileInputStream is = new FileInputStream(f);
            int p = getTotalPages(is);
            System.out.println(p + "\t" + f.getName());
        }
    }
}
